local AvatarFactory = {}

local dbFactory = db.CCFactory:getInstance()
local FileUtils = cc.FileUtils:getInstance()

local ignoreSlots = {"mainweapon", "mainweapon1", "secondweapon", "head", "backdress", "flag"}
dbFactory:setIgnoreSlotsWhenReplacing(ignoreSlots)

local string_find = string.find
local string_format = string.format
local string_split = string.split
local string_gsub = string.gsub

local Armature = import(".Armature")

function AvatarFactory.createFace(faceId, async)
    local faceParams = AvatarFactory._genFaceParams(faceId, async)
    -- dump(faceParams, "createFace")
    if faceParams then
        return Armature.create(faceParams)
    end
end

function AvatarFactory.createHead(headId, faceId, colorId, inBattle, async)
    local headId = headId
    local params = AvatarFactory._genHeadParams(headId,colorId,inBattle)
    if params then
        params.async = async
        -- dump(params, "createHead")
        local headNode = Armature.create(params)
        if headNode and faceId and faceId>0 then
            local faceArmature = AvatarFactory.createFace(faceId, async)
            if faceArmature then
                headNode:setSlotArmature("face",faceArmature)
            end
        end
        return headNode
    end
    return nil
end

function AvatarFactory.getOrCreateEnemyHeroIconPath(params, cb)
	local race = tostring(params.raceID)
	
	if params.raceID and params.raceID > Constant.SpecialRaceID then
        local confs = GD:queryCosplayConfs()
        for k,v in pairs(confs) do
            if v.raceID == race and v.head1 then
				-- head1表示敌方头像
                local filePath = string_format(Res.cosplayIconPath, v.head1)
                if cb then
                    cb(filePath)
                end
                return filePath
            end
        end
    end
end

-- params = {
--    hairID=,
--    faceID=,
--    hairColorID=,
-- }

function AvatarFactory.getPartnerIconPath(params, cb)
    local race = tostring(params.raceID)
    local partnerConfig = GD:queryPartnerByRace(race)
    if partnerConfig then
        local filePath =  GD:getImagePath(Res.partnerIconPath, partnerConfig.icon)
        if cb then
            cb(filePath)
        end
        return filePath
    end
end


function AvatarFactory.getOrCreateHeroIconPath(params,cb)
    local headStr = tostring(params.hairID)
    local faceStr = tostring(params.faceID)
    local colorStr = tostring(params.hairColorID)
    local race = tostring(params.raceID)
    local clipping = true

    -- 角色扮演使用配置表的头像
    -- TODO 应该把角色扮演的的id传进来，不应该轮询表随便获得相同raceID的头像
    if params.raceID and params.raceID > Constant.SpecialRaceID then
        local confs = GD:queryCosplayConfs()
        for k,v in pairs(confs) do
            -- print(v.raceID,v.head,type(v.raceID),type(v.head))
            if v.raceID == race and v.head then

                local filePath = string_format(Res.cosplayIconPath, v.head)
                if cb then
                    cb(filePath)
                end
                return filePath
            end
        end
    end

    local ary = {"heroicon", headStr, faceStr, colorStr .. ".png"}
    local fileName = table.concat(ary, "_")
    local fullPath = app:getStorePath().."/"..fileName
    if FileUtils:isFileExist(fullPath) then
        if cb then
            cb(fullPath)
        end
        return fullPath
    elseif display.isRenderNodeInQueue(fullPath) then
        display.addRenderFileCB(fullPath, function(retPath)
            if cb then
                cb(retPath)
            end
        end)
    else
        local headId = tonumber(params.hairID)
        local faceId = tonumber(params.faceID)
        local colorId = tonumber(params.hairColorID)
        local headNode = AvatarFactory.createHead(headId,faceId,colorId,false,false)
        if not headNode then
            return
        end
        local boundingBox = headNode:getBoundingBox()
        local pointX,pointY = headNode:getBoneLocalPos("point")
        local width =  boundingBox.width - boundingBox.x
        local height = boundingBox.height - boundingBox.y
        local texWidth = 90
        local texHeight = 90
        local scale = 1.4

        local containerNode = display.newLayer() --display.newColorLayer(cc.c4b(255,0,0,255))
        containerNode:setAnchorPoint(cc.p(0.5,0.5))
        containerNode:setContentSize(texWidth,texHeight)

        headNode:setPositionX(texWidth*0.5-pointX)
        headNode:setPositionY(texHeight*0.5 - pointY)

        containerNode:setScaleX(-scale)
        containerNode:setScaleY(scale)
        containerNode:addChild(headNode)
        containerNode:setPosition(-texWidth*0.5,-texHeight*0.5)


        headNode:getAnimation():gotoAndStopByProgress("mapidle",0.6)

        local renderNode = nil

        if clipping then
            local clipNode = cc.ClippingNode:create()
            clipNode:setInverted(false)
            clipNode:setAlphaThreshold(0.5)
            local maskSprite = display.newSprite("public/mask_head.png")

            clipNode:setStencil(maskSprite)
            clipNode:addChild(containerNode)
            clipNode:setPosition(texWidth*0.5,texHeight*0.5)
            renderNode = clipNode
        else
            renderNode = containerNode
            containerNode:setPosition(0,0)
        end

        display.renderNodeToPng(renderNode,texWidth,texHeight,fullPath,function(retPath)
            if cb then
                cb(retPath)
            end
        end)
    end
end

function AvatarFactory.getOrCreateHeroIconSprite(params,cb)
    AvatarFactory.getOrCreateHeroIconPath(params, function(path)
        return cc.Sprite:create(path)
    end)
end

function AvatarFactory.getOrCreateHeroIconClipping(params,stencilNode,cb)
    AvatarFactory.getOrCreateHeroIconPath(params,function(path)
        if tolua.isnull(stencilNode) then
            print("getOrCreateHeroIconClipping fail")
            cb(nil)
        else
            print("getOrCreateHeroIconClipping ok")
            local sprite = cc.Sprite:create()
            sprite:setTexture(path)
            local clipNode = cc.ClippingNode:create()
            clipNode:setInverted(false)
            clipNode:setAlphaThreshold(0.5)
            if stencilNode then
                clipNode:setStencil(stencilNode)
                stencilNode:setVisible(true)
                sprite:setContentSize(stencilNode:getContentSize())
                sprite:setPosition(stencilNode:getPosition())
            end
            clipNode:addChild(sprite)
            cb(clipNode)
        end
    end)
end

function AvatarFactory.createHeadSprite(headArmature, inBattle, cb)
    local node = headArmature
    local actionName = inBattle and "idle" or "mapidle"
    node:getAnimation():gotoAndStopByProgress(actionName, 0.6)
    
    local boundingBox = node:getBoundingBox()
    local posX = boundingBox.x
    local posY = boundingBox.y
    local width =  boundingBox.width
    local height = boundingBox.height

    -- dump(boundingBox, "boundingBox")

    node:setScaleY(-1)
    node:setPosition(-posX, height + posY)

    display.renderNodeToGetTexture(node, width, height, function(texture)
        local sprite = display.newSprite(texture)
        sprite:setPosition(posX, posY)
        sprite:setAnchorPoint(cc.p(0,0))

        local container = display.newLayer()
        container:addChild(sprite)
        display.setCascadeOpacityEnabled(container, true)
        display.setCascadeColorEnabled(container, true)
        executeFunc(cb, container)
    end)
end


local ClassFolders = {
    "1warrior","2mage","3hunter","4rouge","5preist","6","7paladin"
}

function AvatarFactory._genBodyParams(sex,class,race,inBattle)
    local bodyResPath = "armatures/avata"
    local sexFolder = sex==Enums.Sex.female and "female" or "male"
    local classFolder = ClassFolders[tonumber(class)]
    local raceFolder = tostring(race)
    local equipFolder = class.."000"

    local skeletonFile = table.concat( {bodyResPath,sexFolder,classFolder,raceFolder,equipFolder,"skeleton"}, "/")
    local textureFile =table.concat( {bodyResPath,sexFolder,classFolder,raceFolder,equipFolder,"texture"}, "/")
    local armatureName = nil

    if inBattle then
        armatureName="bodybattle"
    else
        armatureName="bodymap"
    end

    local params = {
        skeleton=skeletonFile,
        texture=textureFile,
        armatureName=armatureName
    }
    return  params
end

function AvatarFactory._genHeadParams(headId, colorId, inBattle)
    colorId = colorId or 0
    local params
    local conf = GD:queryHeadResById(headId)
    if conf and conf.head then
        local ary = {conf.head, "/texture"}
        if colorId >0 and colorId <=10 then
            ary[#ary + 1] = string_format("_%02d",colorId)
        else
            --pass
        end
        params = {
            skeleton = conf.head .. "/skeleton",
            texture = table.concat(ary, ""),
            armatureName=(inBattle and "battlehead" or "maphead")
        }
    end
    return params
end

function AvatarFactory._genFaceParams(faceId, async)
    local params
    local conf = GD:queryFaceResById(faceId)
    if conf and conf.face then
        params = {
            path=conf.face,
            armatureName=conf.armature,
            async = async
        }
    end
    return params
end

function AvatarFactory._genMountParams(mountId, async)
    local res = GD:getMountResConfig(mountId)
    if not res then
        return nil
    end

    if not res.dbRes then
        return nil
    end

    local name = res.dbRes
    local path = "armatures/ride/"..name.."/"
    local texture = (res.dbTexture and res.dbTexture or "texture")
    local armatureName = (res.dbArmature and res.dbArmature or name)

    local params = {
        skeleton=path.."skeleton",
        texture=path..texture,
        armatureName=armatureName,
        async = async
    }
    return  params
end

--[[
params = {
    gender, --性别 [required]
    raceID, --种族 [required]
    classID, --职业 [required]
    hairID, --头部ID（头部除脸之外部分） [required]
    faceID, --脸部ID [required]

    hairColorID, --发色 [optional]
    mountID,--坐骑 [optional]
    fashionIDs ,--时装 [optional] {id1,id2,...}
    inBattle , --是否用于战斗
    autoAudio, --自动播放音乐帧

}
async   是否异步加载（目前只用在坐骑）
--]]
function AvatarFactory.createHero(params, async, ignoreMount)
	-- print(debug.traceback("", 1))
    local mountId = tonumber(params.mountID)
    if ignoreMount then
        mountId = 0
    end
    local headId = tonumber(params.hairID)
    local faceId = tonumber(params.faceID)
    local colorId = tonumber(params.hairColorID)
    local sex = params.gender
    local race = params.raceID
    local class = params.classID
    local fashionIDs = params.fashionIDs or {}
    local inBattle = params.inBattle or false

    -- dump(params, "AvatarFactory.createHero")

    -- 1. 创建基础body
    local bodyParams = AvatarFactory._genBodyParams(sex,class,race,inBattle)
    local bodyArmature = Armature.create(bodyParams)
    if not bodyArmature then
        release_print("AvatarFactory.createHero fail",bodyParams.skeleton)
        return nil
    end

    -- 2. 替换装备 考虑到玩具（拥有显示优先级，优先于其他同位置装备的相应外显），需要先剔除和玩具冲突的外显
    local confs = {}
    local toyConf = nil
    local extraHeadPath = nil -- 额外头部，非空会替换掉玩家头部，且忽略头部所有绑点
    for _,fashionId in ipairs(fashionIDs) do
        local conf = GD:getFashionConf(fashionId)
        if conf and conf.part1 then
            if conf.toy == "1" then
                toyConf = conf

                -- 当前只有玩具才会附带额外头部
                if conf.head then
                    local sexStr = sex==Enums.Sex.female and "female" or "male"
                    extraHeadPath = string.format(conf.head, sexStr, race, race)
                end
            else
                confs[#confs + 1] = conf
            end
        end
    end

    -- 3. 创建基础head，替换head
    local isHeadSimple = false
    local headArmature
    if extraHeadPath then
        -- 使用额外的头部，其他在头部的外显全部丢弃
        table.removebyfunc(confs, function(conf)
            return conf.parent1 == "head"
        end, true)

        local headParams = {
            path = extraHeadPath,
            armatureName = (inBattle and "battlehead" or "maphead")
        }
        local headNode = Armature.create(headParams)
        if headNode then
            bodyArmature:setSlotArmature("head",headNode)
        end
    elseif race < Constant.SpecialRaceID then
        -- 1.战斗中都简化 2.战斗外别人简化
        if not EFFECT_ALL_SHOW then
            if inBattle then
                isHeadSimple = true
            else
                if iskindof(params, "UserBaseInfo") and not params:isMe() then
                    isHeadSimple = true
                end
            end
        end
        headArmature = AvatarFactory.createHead(headId,faceId,colorId,inBattle,not isHeadSimple)
    end

    if toyConf then
        -- 移除和玩具同位置的外显
        table.removebyfunc(confs, function(conf)
            return AvatarFactory._checkFashionConfSamePos(toyConf, conf)
        end)
        confs[#confs + 1] = toyConf
    end

    for _,conf in ipairs(confs) do
        AvatarFactory.changeFashion({body=bodyArmature,head=headArmature}, conf, sex, race, class, isHeadSimple)
    end

    if not inBattle then
        AvatarFactory.createGeneral(bodyArmature)
    end

    -- 是否头部骨骼简化
    if headArmature then
        if isHeadSimple then
            AvatarFactory.createHeadSprite(headArmature, inBattle, function(headSprite)
                if cc.isDead(bodyArmature) or cc.isDead(bodyArmature:getArmature()) then return end
                -- print("AvatarFactory.createHeadSprite effect")
                -- 这里有两种方法 1.直接使用head添加 2.重置掉head下slot的display（需要统一骨骼名字，暂不一致）
                local head = bodyArmature:getSlotDisplay("head")
                -- head:setSlotDisplay("headdress", headSprite)
                if head then
                    head:addChild(headSprite)
                else
                    release_dump(params, "AvatarFactory.createHero has not head")
                end
            end)
        else
            bodyArmature:setSlotArmature("head",headArmature)
        end
    end

    local rideArmature = nil
    if mountId and mountId>0 then
        rideArmature = AvatarFactory.createRideById(mountId, async)
        if rideArmature then
            rideArmature:setSlotArmature("role",bodyArmature)
        else
            release_print("=====>AvatarFactory.createRide fail ",mountId)
        end
    end

    return rideArmature or bodyArmature
end

function AvatarFactory.createHeroIgnoreMount(params, async)
    return AvatarFactory.createHero(params, async, true)
end

-- 检测两个时装配置是否是同一位置的
-- part1相同 或者 part1="head"而其他一个包含额外头部
function AvatarFactory._checkFashionConfSamePos(confA, confB)
    local partA = confA.part1
    local partB = confB.part1

    if partA ~= partB then
        -- 部分外显包含额外头部
        if (confA.head and confB.parent1 == "head") or (confB.head and confA.parent1 == "head") then
            return true
        else
            return false
        end
    else
        return true
    end
end


function AvatarFactory.preloadHero(params,cb)
    local mountId = tonumber(params.mountID)
    local headId = tonumber(params.hairID)
    local colorId = tonumber(params.hairColorID)
    local inBattle = params.inBattle or false
    local sex = tonumber(params.gender)
    local race = tonumber(params.raceID)
    local class = tonumber(params.classID)

    local bodyParams = AvatarFactory._genBodyParams(sex,class,race,inBattle)
    local headParams = AvatarFactory._genHeadParams(headId,colorId,inBattle)
    local mountParams = AvatarFactory._genMountParams(mountId)

    local count = 0

    if bodyParams then
        count= count + 1
    end
    if headParams then
        count= count + 2
    end
    if mountParams then
        count= count + 1
    end

    local check = function()
        count = count - 1
        if count == 0 then
            executeFunc(cb, true)
        end
    end

    if bodyParams then
        local skeleton , texture =  display.getArmaturePath(bodyParams)
        dbFactory:loadDragonBonesAsync(skeleton,texture,skeleton,texture,check)
    end
    if headParams then
        -- 头像的资源也算入回调，修复头像简化时没有头发。
        cc.Director:getInstance():getTextureCache():addImageAsync(headParams.texture .. ".png", check)
        local skeleton , texture =  display.getArmaturePath(headParams)
        dbFactory:loadDragonBonesAsync(skeleton,texture,skeleton,texture,check)
    end
    if mountParams then
        local skeleton , texture =  display.getArmaturePath(mountParams)
        dbFactory:loadDragonBonesAsync(skeleton,texture,skeleton,texture,check)
    end
end

function AvatarFactory.createGeneral(armature)
    local generalArmatures = {"yugan1","yugan2","yugan3","yupiao","dinggao","yugou", "fish"}
    local generalPath = "armatures/avata/tools"
    for _,v in ipairs(generalArmatures) do
        local generalArmature = display.newArmature({
            path=generalPath,
            armatureName=v,
            armatureOnly=true
        })
        if generalArmature then
            armature:setSlotArmature(v,generalArmature)
        end
    end
end

-- 修改armature的头部外显（只处理地图里面形象）
-- armature     角色armatrue
-- params       创建角色的avatar params
-- isRide       是否骑乘，用来获取body armature
function AvatarFactory.changeHead(armature, params, isRide)
    if not armature then return end
    local body = armature
    if isRide then
        body = armature:getSlotDisplay("role")
    end

    local headId = tonumber(params.hairID)
    local faceId = tonumber(params.faceID)
    local colorId = tonumber(params.hairColorID)
    local headArmature = AvatarFactory.createHead(headId,faceId,colorId,false)
    if headArmature then
        body:setSlotArmature("head",headArmature)
    end
end

-- 修改armature的面部（只处理地图里面形象）
-- armature     角色armatrue
-- params       创建角色的avatar params
-- isRide       是否骑乘，用来获取body armature
function AvatarFactory.changeFace(armature, params, isRide)
    if not armature then return end
    local body = armature
    if isRide then
        body = armature:getSlotDisplay("role")
    end
    local headArmature = body:getSlotDisplay("head")
    local faceId = tonumber(params.faceID)
    local faceArmature = AvatarFactory.createFace(faceId, false)
    if faceArmature then
        headArmature:setSlotArmature("face",faceArmature)
    end
end

-- 修改armature的部分装备形象（只处理地图里面形象）
-- armature     角色armatrue
-- params       创建角色的avatar params
-- fashionId    要替换的装备形象id
-- isOff        是否是卸装备
-- isRide       是否骑乘，用来获取body armature
function AvatarFactory.changeEquip(armature, params, fashionId, isOff, isRide)
    if not armature then return end
    local body = armature
    if isRide then
        body = armature:getSlotDisplay("role")
    end

    if not body then return end
    
    local head = body:getSlotDisplay("head")
    if not head then return end

    local conf = GD:getFashionConf(fashionId)
    if not conf then return end
    -- 没有外显直接返回
    if not conf.part1 then return end

    local sex = tonumber(params.gender)
    local race = tonumber(params.raceID)
    local class = tonumber(params.classID)
    local headId = tonumber(params.hairID)
    local faceId = tonumber(params.faceID)
    local colorId = tonumber(params.hairColorID)

    -- 玩具，拥有显示优先级，优先于其他同位置装备的相应外显，需检测是否需要替换外显
    -- 额外头部，拥有显示优先级，会替换掉原始头部，且忽略掉头部上外显，只在玩具中会出现，也就是最多有一个
    local isToyConf = conf.toy == "1"         -- 变化的是否是玩具

    local fashionIDs = params.fashionIDs or {}
    local confs = {}
    local existToyConf    --已存在玩具配置
    for i,id in ipairs(fashionIDs) do
        if id ~= fashionId then
            local config = GD:getFashionConf(id)
            if config and config.part1 then
                if config.toy == "1" then
                    existToyConf = config
                else
                    confs[#confs + 1] = config
                end
            end
        end
    end

    -- 当前只有玩具才会附带额外头部
    local isExtraHead = false  -- 变化的是否有额外头部
    local existExtraHeadPath = nil   -- 已存在额外头部
    if isToyConf and conf.head then
        isExtraHead = true
        local sexStr = sex==Enums.Sex.female and "female" or "male"
        existExtraHeadPath = string.format(conf.head, sexStr, race, race)
    end

    -- print("isToyConf, isExtraHead", isToyConf, isExtraHead)
    -- print("existToyConf, existExtraHeadPath", existToyConf, existExtraHeadPath)

    local onConf, offConf
    local onConfs = {} -- 需要穿上的配置
    local offConfs = {} -- 需要移除的配置
    if isOff then
        offConf = conf
        if isToyConf then
            -- 是玩具，卸下之后，还需要穿上被其替换的部位
            for i,v in ipairs(confs) do
                if AvatarFactory._checkFashionConfSamePos(v, conf) then
                    onConfs[#onConfs + 1] = v
                end
            end
        else
            if existToyConf then
                -- 已存在玩具，并且玩具部位相同（包括额外头部） -》忽略掉
                if AvatarFactory._checkFashionConfSamePos(existToyConf, conf) then
                    offConf = nil
                end
            end
        end

        if offConf then
            offConfs[#offConfs + 1] = offConf
        end
    else
        -- 同部位changeFashion会替换掉
        onConf = conf
        if not isToyConf and existToyConf and AvatarFactory._checkFashionConfSamePos(existToyConf,conf) then
            -- 已存在玩具，并且玩具部位相同 -》忽略掉
            onConf = nil
        end

        if onConf then
            onConfs[#onConfs + 1] = onConf
        end
    end

    -- print("#onConfs, #offConfs", #onConfs, #offConfs, fashionId)

    -- 初始化原始骨骼和body，因为卸下装备需要显示原始的
    local bodySkeletonKey, bodyTextureKey
    local headSkeletonKey, headTextureKey
    local function initBodyDragonBone()
        if not bodySkeletonKey then
            local pathParam = {}
            local sexStr = sex==Enums.Sex.female and "female" or "male"
            local classStr = ClassFolders[class]
            pathParam.path = string_format("armatures/avata/%s/%s/%s/%s000", sexStr, classStr, race, class)

            bodySkeletonKey, bodyTextureKey = AvatarFactory._loadDragonBone(pathParam)
        end
    end
    local function initHeadDragonBone()
        if not headSkeletonKey then
            local pathParam = AvatarFactory._genHeadParams(headId, colorId, false)
            headSkeletonKey, headTextureKey = AvatarFactory._loadDragonBone(pathParam)
        end
    end

    -- 如果涉及到头部替换，需要先替换
    if isExtraHead then
        local headArmature
        if isOff then
            headArmature = AvatarFactory.createHead(headId, faceId, colorId, false, false)
        else
            local headParams = {
                path = existExtraHeadPath,
                armatureName = "maphead"
            }
            headArmature = Armature.create(headParams)
        end

        if headArmature then
            body:setSlotArmature("head",headArmature)
            head = headArmature
        else
            return
        end
    end

    -- 卸装备
    for i,offConf in ipairs(offConfs) do
        local parent = offConf["parent1"] or "body"
        local skeletonKey, textureKey
        if parent == "body" then
            initBodyDragonBone()
            skeletonKey, textureKey = bodySkeletonKey, bodyTextureKey
        else
            initHeadDragonBone()
            skeletonKey, textureKey = headSkeletonKey, headTextureKey
        end

        if skeletonKey then
            for i=1,2 do
                local part = offConf["part"..i]
                if part then
                    if parent == "head" then
                        dbFactory:replaceArmatureSlot(head:getArmature(),skeletonKey,textureKey,"maphead",part)
                        if part == "hairdress" then
                            -- hairdress 还需要显示额外的头发
                            dbFactory:replaceArmatureSlot(head:getArmature(),skeletonKey,textureKey,"maphead","hairdress1")
                            dbFactory:replaceArmatureSlot(head:getArmature(),skeletonKey,textureKey,"maphead","hairdress2")
                            dbFactory:replaceArmatureSlot(head:getArmature(),skeletonKey,textureKey,"maphead","hairdress3")
                        end
                    else
                        if part == "clothes" then
                            dbFactory:replaceArmatureSlot(body:getArmature(),skeletonKey,textureKey,"bodymap")
                        else
                            dbFactory:replaceArmatureSlot(body:getArmature(),skeletonKey,textureKey,"bodymap",part)
                        end
                    end
                end
            end
        end
    end

    -- 穿／换装备
    for i,onConf in ipairs(onConfs) do
        AvatarFactory.changeFashion({body=body,head=head}, onConf, sex, race, class)
    end
end

-- 加载骨骼动画和贴图
function AvatarFactory._loadDragonBone(params)
    local skeletonKey, textureKey, skeletonPath, texturePath = display.getArmaturePath(params)

    -- dump(params, "AvatarFactory._loadDragonBone")
    -- print("skeletonKey", skeletonKey)
    -- print("textureKey", textureKey)
    -- print("skeletonPath", skeletonPath)
    -- print("texturePath", texturePath)
    -- printError("AvatarFactory._loadDragonBone")

    local dragonBonesData = dbFactory:loadDragonBonesData(skeletonPath,skeletonKey)
    if dragonBonesData then
        dbFactory:loadTextureAtlasData(texturePath,textureKey)
        return skeletonKey, textureKey
    end
    return nil
end

-- 更换外显
function AvatarFactory.changeFashion(armatures, conf, sex, race, class, isHeadSimple)
    sex = sex or Enums.Sex.female
    race = race or 1

    local scale
    local raceScale = conf.raceScale
    if raceScale then
        local _, _, value = string.find(raceScale, (class * 10 + sex) .. "|(%d+)")
        if value then
            scale = tonumber(value)/10000
        end
    end

    for i=1,2 do
        local part = conf["part"..i]
        if part == nil then break end

        local parentArmature = nil
        local parent = conf["parent"..i]
        local isHead = false
        if parent and parent=="head" then
            isHead = true
            parentArmature = armatures.head
        else
            parentArmature = armatures.body
        end
        if parentArmature == nil then break end

        local armatureFolder = conf["folder"..i] or ""
        armatureFolder  = string_gsub(armatureFolder, "\\", "/")

        local armatureName = conf["armature"..i]
        if armatureName and class then
            --替换%为classid
            --armatureName = string_format(armatureName,tostring(class))
        end

        --替换衣服
        if part == "clothes" then
            -- 如果有大于三个占位符，替换 sex, classFolder, race, class
            -- 如果有二个占位符，替换 sex, race
            local _, count = string_gsub(armatureFolder, "%%s", "%s")
            if count >= 3 then
                local classFolder = ClassFolders[class]
                armatureFolder = string_format(armatureFolder, Enums.SexFileStr[sex], classFolder, race, class)
            else
                armatureFolder = string_format(armatureFolder, Enums.SexFileStr[sex], race)
            end

            local skeletonKey, textureKey = AvatarFactory._loadDragonBone({path=armatureFolder})
            if skeletonKey then
                dbFactory:replaceArmatureDisplay(skeletonKey,textureKey,parentArmature:getArmature())
            else
                printError("load clothes fail. "..armatureFolder)
            end
            break
        end

        if class==1 and part == "secondweapon" then
            local skeletonKey, textureKey = AvatarFactory._loadDragonBone({path=armatureFolder})
            if skeletonKey then
                local slot = parentArmature:getSlot(part)
                if slot then
                    dbFactory:replaceSlotDisplayList(skeletonKey,textureKey,armatureName,part,slot)
                end
            else
                printError("load secondweapon fail. "..armatureFolder)
            end
        else
            if part ~= "mainweapon" and part ~= "secondweapon" then
                local sIndex = string_find(armatureFolder, "%%s")
                if sIndex ~= nil then
                    armatureFolder = string_format(armatureFolder, Enums.SexFileStr[sex], class)
                end
            end

            -- 默认异步，在头部简化骨骼时要同步加载
            local async = true
            if isHead then
                async = not isHeadSimple
            end
            local equipArmature = display.newArmature({
                path=armatureFolder,
                armatureName=armatureName,
                async=async
            })

            if equipArmature then
                if scale then
                    equipArmature:setScale(scale)
                end
                parentArmature:setSlotArmature(part,equipArmature)

                if part == "hairdress" then
                    -- hairdress 还需要隐藏额外的头发 hairdress1,hairdress2,hairdress3
                    parentArmature:setSlotArmature("hairdress1", nil)
                    parentArmature:setSlotArmature("hairdress2", nil)
                    parentArmature:setSlotArmature("hairdress3", nil)
                end
            else
                printError("load equip fail. "..armatureFolder)
            end
        end
    end
end


--[[
创建佣兵
--]]
function AvatarFactory.createMercenaryById(id)
    local res = GD:queryMercRes(id)

    local name = res.dbRes
    local path = "armatures/characters/"..name.."/"
    local textureFile = (res.dbTexture and res.dbTexture or "texture")
    local textureName = name..textureFile
    local armature = (res.dbArmature and res.dbArmature or name)

    local scale = res.scale or 100
    scale = scale*0.01

    local armature = Armature.create(
     {
        skeleton=path.."skeleton",
        texture=path..textureFile,
        armatureName=armature
    })

    if not armature then
        armature = Armature.create({path="armatures/characters/harvestholem",armatureName="harvestholem",animationName="",skeletonName="harvestholem",autoAudio=true})
        local missingLabel = display.newTTFLabel({
            text = "merc: "..id.." missing",
            size = 22,
            color = display.COLOR_WHITE,
            align = cc.TEXT_ALIGNMENT_CENTER,
            valign = cc.VERTICAL_TEXT_ALIGNMENT_CENTER})
        armature:addChild(missingLabel)
    end

    armature:setScale(scale)

    return armature
end

function AvatarFactory._getCreateNpcParams(id)
    local res = GD:queryNpcRes(id) or {}

    local name = res.dbRes or "harvestholem"
    local path = name.."/"
    local texture = (res.dbTexture and res.dbTexture or "texture")

    local params = {
        skeleton = path.."skeleton",
        texture = path..texture,
    }
    return params
end

function AvatarFactory.preloadNpcById(id, cb)
    local params = AvatarFactory._getCreateNpcParams(id)
    local skeleton, texture = display.getArmaturePath(params)
    dbFactory:loadDragonBonesAsync(skeleton,texture,skeleton,texture,function()
            executeFunc(cb)
        end)
end

function AvatarFactory.createNpcById(id,bInbattle)
    local res = GD:queryNpcRes(id) or {}

    local name = res.dbRes or "harvestholem"
    local path = name.."/"
    local texture = (res.dbTexture and res.dbTexture or "texture")
    local armatureName = ((res.dbArmature and string.len(string.trim(res.dbArmature))>0) and res.dbArmature or name)
    armatureName = bInbattle and (res.dbArmatureInBattle or armatureName) or armatureName
    local textureName = name..texture
    local scale = res.scale or 100
    scale = scale*0.01

    local armature = Armature.create(
    {
        skeleton=path.."skeleton",
        texture=path..texture,
        armatureName=armatureName,
        animationName="",
        autoAudio=true
    })

    if not armature then
        armature = Armature.create({path="armatures/characters/aidi",armatureName="aidi",animationName=""})
        local missingLabel = display.newTTFLabel({
            text = "npc: "..id.." missing",
            size = 28,
            color = display.COLOR_WHITE,
            align = cc.TEXT_ALIGNMENT_CENTER,
            valign = cc.VERTICAL_TEXT_ALIGNMENT_CENTER})
        armature:addChild(missingLabel)
        missingLabel:setPositionY(-20)
    end

    armature:setScale(scale)

    if res.color then
        local colorList = string_split(res.color,",")
        if #colorList == 3 then
            local r,g,b = tonumber(colorList[1]),tonumber(colorList[2]),tonumber(colorList[3])
            armature:setDefaultColor(cc.c3b(r,g,b))
        end
    end

    return armature
end

-- async 是否异步加载，默认是true
function AvatarFactory.createRideById(id, async)
    local params = AvatarFactory._genMountParams(id, async)
    if not params then
        printError("createRideById fail "..id)
        return nil
    end

    local armature = Armature.create(params)

    if not armature then
        armature = Armature.create({path="armatures/characters/aidi",armatureName="aidi",animationName=""})
        local missingLabel = display.newTTFLabel({
            text = "ride: "..id.." missing",
            size = 28,
            color = display.COLOR_WHITE,
            align = cc.TEXT_ALIGNMENT_CENTER,
            valign = cc.VERTICAL_TEXT_ALIGNMENT_CENTER})
        armature:addChild(missingLabel)
        missingLabel:setPositionY(-20)
    end

    return armature

end

function AvatarFactory._getPartnersDragonBoneParams(params)
    local basePath = "armatures/avata"
    local genderNum = tonumber(params.gender)
    local sexFolder = genderNum==Enums.Sex.female and "female" or "male"
    local classIdNum = tonumber(params.classID)
    local classFolder = ClassFolders[classIdNum]
    local raceId = params.raceID
    local fullPath = string.format("%s/%s/%s/%s/%s/", basePath, sexFolder, classFolder, raceId, classIdNum * 1000)
    local skeleton=fullPath.."skeleton"
    local texture=fullPath.."texture"
    return skeleton, texture
end

function AvatarFactory.preloadPartnerByConf(params, cb)
    local skeleton, texture = AvatarFactory._getPartnersDragonBoneParams(params)
    skeleton, texture = display.getArmaturePath({skeleton = skeleton, texture = texture,})
    dbFactory:loadDragonBonesAsync(skeleton,texture,skeleton,texture,function()
        executeFunc(cb)
    end)
end

-- 创建 partner
function AvatarFactory.createPartnerByConf(params, isInBattle, scale)
    local armatureName
    if isInBattle then
        armatureName = params.dbArmatureInBattle or "bodybattle"
    else
        armatureName = params.dbArmature or "bodymap"
    end

    local aScale = scale
    if not aScale then
        aScale = 1
    end
    local skeleton, texture = AvatarFactory._getPartnersDragonBoneParams(params)
    local armature = Armature.create(
     {
        skeleton=skeleton,
        texture=texture,
        armatureName=armatureName,
        autoAudio=true
    })

    if not armature then
        armature = Armature.create({path="armatures/characters/harvestholem",armatureName="harvestholem",animationName="",skeletonName="harvestholem",autoAudio=true})
        local missingLabel = display.newTTFLabel({
            text = "merc: "..id.." missing",
            size = 22,
            color = display.COLOR_WHITE,
            align = cc.TEXT_ALIGNMENT_CENTER,
            valign = cc.VERTICAL_TEXT_ALIGNMENT_CENTER})
        armature:addChild(missingLabel)
    end
    armature:setScale(aScale)
    return armature
end

--[[
    npcKind = NpcKind_Partner
        gender 
        classID 
        raceID 
    else
        id

]]
function AvatarFactory.createNpcOrPartnersByParams(params, isInBattle)
    if params.npcKind == Enums.NpcKind.NpcKind_Partner then
        return AvatarFactory.createPartnerByConf(params, isInBattle)
    else
        return AvatarFactory.createNpcById(params.id, isInBattle)
    end
end


return AvatarFactory
